Analisi delle Correlazioni - Questionario sui Giovani e la Città di Bari¶
Obiettivo dell'Analisi¶
Questo notebook analizza i dati raccolti attraverso un questionario rivolto ai residenti a Bari. L'obiettivo è identificare le correlazioni tra diverse variabili per comprendere meglio.
Cosa sono le Correlazioni?¶
Una correlazione misura quanto due variabili sono collegate tra loro:
- +1: correlazione perfetta positiva (quando una aumenta, anche l'altra aumenta)
- 0: nessuna correlazione (le variabili sono indipendenti)
- -1: correlazione perfetta negativa (quando una aumenta, l'altra diminuisce)
In questo studio consideriamo significative le correlazioni ≥ 0.4 (moderate-forti).
# Importazione delle librerie necessarie per l'analisi
import pandas as pd # Per manipolare i dati in formato tabellare
import warnings
warnings.simplefilter(action='ignore', category=pd.errors.PerformanceWarning) # Nasconde avvisi non critici
from sklearn.preprocessing import MultiLabelBinarizer # Per convertire risposte multiple in variabili binarie
import re # Per manipolare testo con espressioni regolari
import json # Per salvare risultati in formato JSON
1. Preparazione dei Dati sugli Spazi¶
Prima parte dell'analisi: processare i dati relativi agli spazi frequentati dai rispondenti.
Il file "spazi mapped.csv" contiene informazioni sui luoghi che i giovani frequentano a Bari, mappati in categorie specifiche.
# Caricamento del file con i dati sugli spazi mappati
spazi = pd.read_csv("spazi mapped.csv")
# TRASFORMAZIONE DEI DATI DEGLI SPAZI
# Il file contiene una colonna 'Map' con valori separati da virgole (es: "Bar, Università, Centro commerciale")
# Dobbiamo trasformare questi dati in colonne separate per ogni tipo di spazio
# Prima, raccogliamo tutti i tipi di spazi unici presenti nei dati
all_values = set() # Un set per evitare duplicati
for values in spazi['Map'].dropna(): # Per ogni riga non vuota nella colonna Map
# Dividiamo i valori per virgola e aggiungiamo al set
all_values.update([v.strip() for v in values.split(',')])
# Ora creiamo una colonna binaria (0/1) per ogni tipo di spazio
for value in all_values:
# Per ogni tipo di spazio, creiamo una colonna "Luogo [tipo]"
# che vale 1 se il rispondente frequenta quel tipo di spazio, 0 altrimenti
spazi[f"Luogo {value}"] = spazi['Map'].apply(
lambda x: 1 if pd.notna(x) and value in [v.strip() for v in x.split(',')] else 0
)
# Rimuoviamo la colonna originale 'Map' perché ora abbiamo le colonne separate
spazi.drop('Map', axis=1, inplace=True)
2. Preparazione dei Dati del Questionario¶
Seconda parte: processare le risposte del questionario.
Il questionario contiene domande di diverso tipo:
- Domande a scelta singola (es: età, genere)
- Domande a scelta multipla (es: eventi a cui si partecipa)
- Scale di valutazione (es: livello di soddisfazione)
Tutte queste risposte devono essere convertite in variabili numeriche per poter calcolare le correlazioni.
# Caricamento del file Excel con le risposte al questionario
questionario = pd.read_excel("questionario anonimo.xlsx")
# Rimozione di colonne non necessarie per l'analisi
questionario.drop(["@dropdown"], axis=1, inplace=True)
# DEFINIZIONE DEI TIPI DI DOMANDE
# Identifichiamo quali colonne (domande) richiedono quale tipo di elaborazione
# Domande a scelta singola - verranno convertite con "one-hot encoding"
# (ogni possibile risposta diventa una colonna separata con valore 0 o 1)
toHotEncode = [0, 1, 2, 3, 4, 6, 7, 11, 12, 13, 14, 19, 20, 22, 23]
# Domande a scelta multipla - prima divise poi convertite
toSplitAndHotEncode = [5, 15, 18]
# Otteniamo i nomi delle colonne da processare
columns_to_encode = [questionario.columns[i] for i in toHotEncode]
# ONE-HOT ENCODING PER DOMANDE A SCELTA SINGOLA
# Esempio: se la domanda "Genere" ha risposte ["Maschio", "Femmina", "Altro"]
# diventa tre colonne: "Genere_Maschio", "Genere_Femmina", "Genere_Altro"
# con valori 1 per la risposta data e 0 per le altre
encoded_df = pd.get_dummies(questionario[columns_to_encode + ["Quali?"]],
columns=columns_to_encode,
prefix_sep=' ') # Separatore tra nome domanda e risposta
# Identifichiamo le colonne rimanenti (quelle non ancora processate)
remaining_columns = [col for i, col in enumerate(questionario.columns) if i not in toHotEncode]
# FUNZIONE PER PROCESSARE DOMANDE A SCELTA MULTIPLA
# Alcune domande permettono di selezionare più risposte
# (es: "Quali eventi frequenti?" -> "Concerti, Teatro, Mostre")
def onehot_encode_multiselect_columns(df, columns_to_encode):
"""
Converte colonne con risposte multiple in colonne binarie separate.
Esempio di trasformazione:
"Concerti, Teatro" -> [Concerti: 1, Teatro: 1, Mostre: 0, ...]
"""
# Iniziamo con la colonna identificativa "Quali?"
df_encoded = df[["Quali?"]].copy()
for col in columns_to_encode:
# Otteniamo il nome della colonna
if isinstance(col, int):
col_name = df.columns[col]
else:
col_name = col
# Dividiamo le risposte multiple usando le virgole
responses = df[col_name].fillna('').astype(str)
# Funzione per dividere correttamente il testo
# Dividiamo su ", " seguito da lettera maiuscola
def split_on_comma_capital(text):
parts = re.split(r', (?=[A-Z])', text)
return parts
split_responses = responses.apply(split_on_comma_capital)
# Puliamo i dati rimuovendo spazi extra e valori vuoti
cleaned_responses = []
for response_list in split_responses:
cleaned = [item.strip() for item in response_list if item.strip()]
cleaned_responses.append(cleaned if cleaned else [''])
# Raccogliamo tutte le possibili risposte
all_choices = set()
for response_list in cleaned_responses:
all_choices.update(response_list)
all_choices.discard('') # Rimuoviamo la stringa vuota
# Creiamo colonne binarie per ogni possibile risposta
mlb = MultiLabelBinarizer()
encoded_matrix = mlb.fit_transform(cleaned_responses)
# Creiamo i nomi delle colonne con il prefisso della domanda originale
encoded_columns = [f"{col_name} {choice}" for choice in mlb.classes_]
# Creiamo il DataFrame con le colonne codificate
encoded_df_temp = pd.DataFrame(encoded_matrix, columns=encoded_columns, index=df.index)
# Aggiungiamo le nuove colonne al risultato finale
df_encoded = pd.concat([df_encoded, encoded_df_temp], axis=1)
return df_encoded
# Applichiamo la funzione alle domande a scelta multipla
df_encoded = onehot_encode_multiselect_columns(questionario, toSplitAndHotEncode)
# PULIZIA DEI DATI
# Rimuoviamo le righe senza identificativo (colonna "Quali?")
# Questo assicura che abbiamo solo risposte complete
encoded_df.dropna(subset=["Quali?"], inplace=True)
df_encoded.dropna(subset=["Quali?"], inplace=True)
3. Unione dei Dataset¶
Ora uniamo tutti i dati processati in un dataset finale che conterrà:
- Risposte del questionario (convertite in formato numerico)
- Informazioni sui luoghi frequentati
- Tutte le variabili pronte per l'analisi delle correlazioni
# Uniamo i due dataframe processati usando la colonna "Quali?" come chiave
finalDf = pd.merge(encoded_df, df_encoded, on="Quali?", how="outer")
# Puliamo anche il dataset degli spazi
spazi.dropna(subset=["Quali?"], inplace=True)
# Rimuoviamo colonne duplicate o non necessarie dal dataset spazi
spazi.drop(['Quanto ti senti accoltə negli spazi che frequenti?'], axis=1, inplace=True)
# Uniamo anche i dati sui luoghi frequentati
finalDf = pd.merge(finalDf, spazi, on="Quali?", how="outer")
# Rimuoviamo la colonna identificativa perché non serve più
finalDf.drop(["Quali?"], axis=1, inplace=True)
4. Calcolo delle Correlazioni¶
Questa è la parte centrale dell'analisi. Calcoleremo le correlazioni tra tutte le variabili per identificare:
- Quali caratteristiche demografiche influenzano le opinioni
- Come le abitudini di frequentazione si collegano alla soddisfazione
- Quali fattori sono più importanti per il benessere dei giovani baresi
Come Leggere le Correlazioni¶
- 0.4 - 0.6: correlazione moderata
- 0.6 - 0.8: correlazione forte
- 0.8 - 1.0: correlazione molto forte
- Valori negativi: correlazione inversa (una aumenta, l'altra diminuisce)
# ELENCO DELLE DOMANDE DEL QUESTIONARIO
# Ogni elemento rappresenta una categoria di domande che analizzeremo
questions = [
"Di che genere sei?",
"Quanti anni hai?",
"Qual è la tua occupazione?",
"Che rapporto hai con la città di Bari?",
"Per te partecipare a incontri di aggregazione sociale, eventi culturali e dibattiti politici è:",
"Bari offre spazi di aggregazione sociale e culturale?",
"Ritieni che Bari sia una città a misura di persone Under 30?",
"Quanto ti senti accoltə negli spazi che frequenti?",
"Ritieni che questi spazi conoscano e siano attenti ai bisogni della comunità che li frequentano?",
"Come giudichi il dialogo tra questi spazi?",
"Partecipi a eventi culturali a Bari?",
"Hai mai sentito parlare di Scomodo?",
"Se sì, ti piacerebbe una Redazione di Scomodo a Bari?",
"Come incide sul tuo benessere il rapporto che hai con la città di Bari?",
"Sei a Bari, sei felice?",
"A cosa è dovuta, se c'è, la tua difficoltà nella partecipazione?",
"A che tipo di eventi partecipi?",
"Come pensi che vadano gestiti questi fenomeni?",
"Luogo"
]
# FUNZIONE PER CALCOLARE LA MATRICE DI CORRELAZIONE
def generate_correlation_matrix(df, columns_set1, columns_set2, method='pearson'):
"""
Calcola la correlazione tra due gruppi di variabili.
Parametri:
- df: il dataset
- columns_set1: primo gruppo di variabili
- columns_set2: secondo gruppo di variabili
- method: tipo di correlazione ('pearson' è il più comune)
Restituisce una matrice con i valori di correlazione.
"""
# Verifichiamo che tutte le colonne esistano nel dataset
missing_cols1 = set(columns_set1) - set(df.columns)
missing_cols2 = set(columns_set2) - set(df.columns)
if missing_cols1:
raise ValueError(f"Columns not found in set 1: {missing_cols1}")
if missing_cols2:
raise ValueError(f"Columns not found in set 2: {missing_cols2}")
# Convertiamo i valori booleani in numeri (True=1, False=0)
# per poter calcolare le correlazioni
df_numeric = df[columns_set1 + columns_set2].astype(int)
# Creiamo la matrice di correlazione vuota
correlation_matrix = pd.DataFrame(
index=columns_set1,
columns=columns_set2
)
# Calcoliamo la correlazione per ogni coppia di variabili
for col1 in columns_set1:
for col2 in columns_set2:
correlation_matrix.loc[col1, col2] = df_numeric[col1].corr(
df_numeric[col2], method=method
)
# Convertiamo in formato numerico
correlation_matrix = correlation_matrix.astype(float)
return correlation_matrix
# ORGANIZZAZIONE DELLE VARIABILI PER DOMANDA
# Raggruppiamo tutte le colonne che appartengono a ciascuna domanda
# Esempio: "Di che genere sei?" potrebbe avere colonne come
# "Di che genere sei? Maschio", "Di che genere sei? Femmina", ecc.
prefix_to_columns = {}
for question in questions:
# Trova tutte le colonne che iniziano con questa domanda
matching_columns = [col.replace(question + " ", "") for col in finalDf.columns if col.startswith(question)]
prefix_to_columns[question] = matching_columns
# FUNZIONE AUSILIARIA PER PULIRE I NOMI DEI FILE
def sanitize_filename(filename):
"""Rimuove caratteri non validi dai nomi dei file"""
invalid_chars = '<>:"|?*/'
for char in invalid_chars:
filename = filename.replace(char, '')
return filename.strip()
# FUNZIONE PER CREARE GRAFICI DELLE CORRELAZIONI
# (Questa funzione è definita ma non utilizzata nel flusso principale)
import matplotlib.pyplot as plt
import seaborn as sns
import numpy as np
def plot_correlation_heatmap(correlation_matrix, title="Correlation Matrix", figsize=(12, 8),
cmap='RdBu_r', center=0, annot=True, fmt='.2f',
cbar_kws=None, save_path=None):
"""
Crea un grafico a mappa di calore per visualizzare le correlazioni.
I colori rappresentano l'intensità della correlazione:
- Rosso: correlazione negativa
- Bianco: nessuna correlazione
- Blu: correlazione positiva
"""
# Configura il grafico
fig, ax = plt.subplots(figsize=figsize)
# Crea la mappa di calore
sns.heatmap(
correlation_matrix,
annot=annot, # Mostra i valori numerici
cmap=cmap, # Schema di colori
center=center, # Centro della scala di colori
fmt=fmt, # Formato dei numeri
square=False,
cbar_kws=cbar_kws or {},
ax=ax
)
# Personalizza il grafico
ax.set_title(title, fontsize=14, fontweight='bold', pad=20)
# Ruota le etichette per una migliore leggibilità
ax.set_xticklabels(ax.get_xticklabels(), rotation=45, ha='right')
ax.set_yticklabels(ax.get_yticklabels(), rotation=0)
# Adatta il layout per evitare che le etichette vengano tagliate
plt.tight_layout()
# Salva se richiesto
if save_path:
plt.savefig(save_path, dpi=300, bbox_inches='tight')
# Mostra il grafico
plt.show()
return fig, ax
5. Estrazione delle Correlazioni Significative¶
Ora calcoliamo tutte le possibili correlazioni e filtriamo solo quelle significative (≥ 0.4).
Il processo:
- Calcola correlazioni tra ogni coppia di domande
- Mantiene solo quelle con correlazione ≥ 0.4
- Seleziona i risultati più interessanti per ogni domanda
- Organizza i dati per la visualizzazione finale
# CODICE COMMENTATO - VERSIONE CHE SALVA FILE SEPARATI
# Questo codice salverebbe ogni matrice di correlazione in un file separato
# e creerebbe grafici per ogni coppia significativa di domande
# os.makedirs("matrices/", exist_ok=True)
# for question1, values1 in prefix_to_columns.items():
# for question2, values2 in prefix_to_columns.items():
# if question1 != question2:
# features = [question1 + " " + val for val in values1]
# targets = [question2 + " " + val for val in values2]
# try:
# corr_matrix = generate_correlation_matrix(finalDf, features, targets)
# if corr_matrix.max().max() >= 0.4:
# corr_matrix.to_json("matrices/" + sanitize_filename(question1) + "-" + sanitize_filename(question2) + ".json", index=False, indent=4, force_ascii=False)
# plot_correlation_heatmap(
# corr_matrix,
# title="Correlation Matrix: Features vs Targets",
# figsize=(8, 6)
# )
# except Exception as e:
# print ("Failed to process matrix for", question1, "and", question2)
# CALCOLO DI TUTTE LE CORRELAZIONI SIGNIFICATIVE
# Questo ciclo calcola le correlazioni tra ogni coppia di domande
# e mantiene solo quelle con correlazione massima ≥ 0.4
out = {} # Dizionario per memorizzare i risultati
for question1, values1 in prefix_to_columns.items():
for question2, values2 in prefix_to_columns.items():
if question1 != question2: # Non correliamo una domanda con se stessa
# Ricostruiamo i nomi completi delle colonne
features = [question1 + " " + val for val in values1]
targets = [question2 + " " + val for val in values2]
try:
# Calcoliamo la matrice di correlazione
corr_matrix = generate_correlation_matrix(finalDf, features, targets)
# Manteniamo solo se c'è almeno una correlazione significativa
if corr_matrix.max().max() >= 0.4:
# Salviamo in formato JSON per elaborazione successiva
key = sanitize_filename(question1) + "-" + sanitize_filename(question2)
out[key] = corr_matrix.to_json(index=False, indent=4, force_ascii=False)
except Exception as e:
# Stampiamo errori ma continuiamo l'elaborazione
print ("Failed to process matrix for", question1, "and", question2)
6. Organizzazione Finale dei Risultati¶
Ultimo passo: organizziamo i risultati in un formato facilmente utilizzabile per creare visualizzazioni e report.
Il processo:
- Elimina duplicati: evita di avere sia "A vs B" che "B vs A"
- Seleziona i top risultati: per ogni domanda, mantiene solo le correlazioni più forti
- Pulisce i nomi: rimuove testo ridondante per una migliore leggibilità
- Salva in formato JSON: per utilizzo in dashboard o altre applicazioni
# RIMOZIONE DEI DUPLICATI
# Alcune coppie di domande appaiono due volte (A-B e B-A)
# Manteniamo solo una versione per evitare ridondanza
processed_pairs = set() # Tiene traccia delle coppie già processate
filtered_out = {} # Risultati filtrati
for title in out.keys():
split = title.split("-")
# Normalizziamo l'ordine delle domande per identificare duplicati
pair = tuple(sorted([split[0].strip(), split[1].strip()]))
if pair not in processed_pairs:
processed_pairs.add(pair)
filtered_out[title] = out[title]
# ORGANIZZAZIONE FINALE DEI DATI
# Processamento dei risultati filtrati per creare la struttura finale
full = {}
for title, x in filtered_out.items():
value = json.loads(x) # Convertiamo da JSON a dizionario Python
split = title.split("-")
# Struttura base per ogni coppia di domande
full[title] = {
"axis1": split[1].strip(), # Prima domanda
"axis2": split[0].strip(), # Seconda domanda
"data": {} # Dati delle correlazioni
}
# SELEZIONE DEI TOP RISULTATI
# Per ogni domanda, selezioniamo le 10 correlazioni più forti
top_k1_keys = sorted(
value.items(),
key=lambda kv: max(abs(v) for v in kv[1].values()), # Ordina per correlazione massima
reverse=True
)[:10] # Prendi i primi 10
# Riordina alfabeticamente per consistenza
top_k1_keys = sorted(top_k1_keys, key=lambda kv: kv[0])
for k1, k2v in top_k1_keys:
# Pulizia del nome della prima variabile
newK1 = k1.replace(split[1], "").replace("?", "").replace(": ", "").strip()
# Selezione delle top correlazioni per la seconda variabile
top_k2_items = sorted(
k2v.items(),
key=lambda kv: abs(kv[1]), # Ordina per valore assoluto della correlazione
reverse=True
)[:10] # Prendi i primi 10
# Riordina alfabeticamente
top_k2_items = sorted(top_k2_items, key=lambda kv: kv[0])
for k2, v in top_k2_items:
# Pulizia del nome della seconda variabile
newK2 = k2.replace(split[0], "").replace("?", "").strip()
# Aggiunta al risultato finale
if newK1 not in full[title]["data"]:
full[title]["data"][newK1] = {}
full[title]["data"][newK1][newK2] = v
# Ordina il dizionario finale alfabeticamente
full = dict(sorted(full.items()))
7. Salvataggio dei Risultati¶
Salviamo i risultati dell'analisi in un file JSON chiamato toPlot.json.
Struttura del File di Output¶
Il file contiene:
- Chiavi: nomi delle coppie di domande correlate
- axis1/axis2: le due domande messe in relazione
- data: matrice con i valori di correlazione specifici
Come Utilizzare i Risultati¶
Questo file può essere utilizzato per:
- Creare dashboard interattive
- Generare report automatici
- Identificare pattern interessanti nei dati
- Guidare ulteriori ricerche qualitative
# SALVATAGGIO DEL FILE FINALE
# Salviamo tutti i risultati in un file JSON per utilizzo successivo
with open ("toPlot.json", "w", encoding='utf-8') as f:
json.dump(full, f, indent=4, ensure_ascii=False)